#include "AsioServerImp.h" 

#include <iostream> //for std::cout (fake logger)

#include <boost/bind/bind.hpp>

#include "AsioWriteBuffer.h"
#include "Util.h"
#include "AsioTimerTask.h"

using boost::asio::ip::tcp;


namespace frame {
namespace imp {
namespace asio {

AsioServerChannelImp::AsioServerChannelImp(uint64_t id, ServerImp * serverImp)
: ServerChannelImp(id,  serverImp)
, writeBuffer(new AsioWriteBuffer)
, socket(static_cast<AsioServerImp*>(serverImp)->getIoContext())
{
}

AsioServerChannelImp::~AsioServerChannelImp(){
    this->close();
    std::cout << "free channel" << std::endl;
}

AsioServerChannelImp::pointer AsioServerChannelImp::newInstance(ServerImp * serverImp){
    assert(serverImp != nullptr);
    return pointer(new AsioServerChannelImp(serverImp->getChannelNumber().next(), serverImp));
}

std::string AsioServerChannelImp::getRemoteIp(){
    return socket.remote_endpoint().address().to_string();
}
const int AsioServerChannelImp::getRemotePort(){
    return socket.remote_endpoint().port();
}
bool AsioServerChannelImp::open(){
    assert(getServerImp() != nullptr);

    auto * handler = getServerImp()->getHandler();
    if(handler != nullptr){
        handler->connected(this);
    }

    startRead();
    return true;
}
bool AsioServerChannelImp::close(){
    std::cout << "channel closed" << std::endl;
    if(socket.is_open()){
        std::cout << "release source && socket closed" << std::endl;
        socket.close();
    }
    return true;
}

void AsioServerChannelImp::startRead(){
    socket.async_read_some(boost::asio::buffer(data),
            boost::bind(&AsioServerChannelImp::handleRead, shared_from_this(),
                boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred)
        );
}
void AsioServerChannelImp::handleRead(const boost::system::error_code & error, size_t bytes_transferred){
    assert(getServerImp() != nullptr);

    auto * handler = getServerImp()->getHandler();
    if(!error){
        if(handler != nullptr){
            handler->read(this, (const char*)data.elems, bytes_transferred);
        }
        startRead();
        return ;
    }
    else{
        switch(error.value()){
            //passive close
            case boost::asio::error::eof: {
                if(handler != nullptr){
                    handler->disconnected(this, Error::PassiveClose);
                }
                //this->close(); //release the resource
                break;
            }
            //active close
            case boost::asio::error::bad_descriptor:{
                if(handler != nullptr){
                    handler->disconnected(this, Error::ActiveClose);
                }
                //this->close(); //release the resource
                break;
            }
            default:
                std::cout << "handle read got error:[" << error << "]" << std::endl;
                if(handler != nullptr){
                    handler->readException(this, error.value()); //TODO: map asio error (?) to ? 
                    handler->disconnected(this, Error::ActiveClose);
                }
                this->close(); //release the resource
            break;
        }
    }
}
void AsioServerChannelImp::startWrite(std::shared_ptr<AsioWriteBuffer> writeBufferOnce){
    socket.async_write_some(*writeBufferOnce,
            boost::bind(&AsioServerChannelImp::handleWrited, shared_from_this(),
                boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred)
    );
}
void AsioServerChannelImp::handleWrited(const boost::system::error_code & error, size_t bytes_transferred){
    assert(getServerImp() != nullptr);
    auto * handler = getServerImp()->getHandler();
    if(!error){
        if(handler != nullptr){
            handler->writed(this, bytes_transferred);
        }
        //TODO: bytes_transferred < writeBufferOnce.length => 
    }
    else{
        switch(error.value()){
            //TODO:
            default:{
                std::cout << "handle write got error:[" << error << "]" << std::endl;
                if(handler != nullptr){
                    handler->writeException(this, error.value()); //TODO: map asio error(?) to ?
                    handler->disconnected(this, Error::ActiveClose);
                }
                this->close(); //release the resource
                break;
            }
        }
    }
}

void AsioServerChannelImp::disconnect(){
    if(socket.is_open()){
        socket.close();
    }
}
void AsioServerChannelImp::write(const char * buf, int size){
    auto toWrite = std::make_shared<AsioWriteBuffer>();
    {
        std::lock_guard<std::mutex> lock(channelMutex);
        writeBuffer->pushBack(buf, size);
        toWrite.swap(writeBuffer);
    }

    startWrite(toWrite);
}






















AsioServerImp::AsioServerImp(const int port)
: ServerImp(port)
, ioContext()
, acceptor(ioContext, tcp::endpoint(tcp::v4(), port))
, timerNumber(new UniqueNumber)
, timerTasks(new AsioTimerTasks(ioContext))
{
}

AsioServerImp::~AsioServerImp(){
    if(timerTasks != nullptr){
        delete timerTasks;
    }
    if(timerNumber != nullptr){
        delete timerNumber;
    }
}
int AsioServerImp::start(){
    ServerImp::start(); // insert defaultHandler

    startAccept();
    startTimer();
    return 0;
}
int AsioServerImp::run(){
    ioContext.run();
    return 0;
}
void AsioServerImp::stop(){
    ioContext.stop();
}

void AsioServerImp::startAccept(){
    auto newServerChannelImp = AsioServerChannelImp::newInstance(this);

    //TODO:callback 

    acceptor.async_accept(newServerChannelImp->getSocket(),
            boost::bind(&AsioServerImp::handleAccept, this, newServerChannelImp,
                boost::asio::placeholders::error));
}

void AsioServerImp::handleAccept(AsioServerChannelImp::pointer newServerChannelImp,
        const boost::system::error_code& error){

    if (!error)
    {
        newServerChannelImp->open();
    }
    else{
        //TODO: error handle
        std::cerr << "accept got an error:" << error << std::endl;
    }
    startAccept();
}

void AsioServerImp::startTimer(){
    for(auto & pair : *timerTasks){
        std::shared_ptr<AsioTimer> asioTimer = std::static_pointer_cast<AsioTimer>(pair.second);
        if(asioTimer != nullptr)
            asioTimer->startTimer();
    }
}



uint64_t AsioServerImp::addTimerTask(ITimerTask * task, uint64_t timespan){
    assert(task != nullptr);

    uint64_t timerId = timerNumber->next(); 
    assert(timerTasks->contains(timerId) == false);
    task->setTimerId(timerId);
    
    timerTasks->insertNoFind(timerId, task, timespan);
    return timerId;
}
void AsioServerImp::removeTimerTask(uint64_t timerId){
    auto timer = timerTasks->remove(timerId); 
    if(timer != nullptr){
        timer->stopTimer();
    }
}

} //end of namespace asio
} //end of namespace imp
} //end of namespace  frame
